import Cesium from './Cesium'
import WeFramebuffer from './WeFramebuffer';

let renderStateScratch;
const drawCommandScratch = new Cesium.DrawCommand({
  primitiveType: Cesium.PrimitiveType.TRIANGLES,
});
const clearCommandScratch = new Cesium.ClearCommand({
  color: new Cesium.Color(0.0, 0.0, 0.0, 0.0),
});

function createFramebuffer(context, outputTexture) {
  return new WeFramebuffer({
    context: context,
    colorTextures: [outputTexture],
    destroyAttachments: false,
  });
}

function createViewportQuadShader(context, fragmentShaderSource) {
  return Cesium.ShaderProgram.fromCache({
    context: context,
    vertexShaderSource: Cesium._shadersViewportQuadVS,
    fragmentShaderSource: fragmentShaderSource,
    attributeLocations: {
      position: 0,
      textureCoordinates: 1,
    },
  });
}

function createRenderState(width, height) {
  if (
    !Cesium.defined(renderStateScratch) ||
    renderStateScratch.viewport.width !== width ||
    renderStateScratch.viewport.height !== height
  ) {
    renderStateScratch = Cesium.RenderState.fromCache({
      viewport: new Cesium.BoundingRectangle(0, 0, width, height),
    });
  }
  return renderStateScratch;
}

class WeComputeEngine extends Cesium.ComputeEngine {
  constructor(options) {
    super(options)
  }
  execute = function (computeCommand) {
    //>>includeStart('debug', pragmas.debug);
    Cesium.Check.defined("computeCommand", computeCommand);
    //>>includeEnd('debug');

    // This may modify the command's resources, so do error Cesium.Checking afterwards
    if (Cesium.defined(computeCommand.preExecute)) {
      computeCommand.preExecute(computeCommand);
    }

    //>>includeStart('debug', pragmas.debug);
    if (
      !Cesium.defined(computeCommand.fragmentShaderSource) &&
      !Cesium.defined(computeCommand.shaderProgram)
    ) {
      throw new Cesium.DeveloperError(
        "computeCommand.fragmentShaderSource or computeCommand.shaderProgram is required.",
      );
    }

    //>>includeStart('debug', pragmas.debug);
    if (
      !Cesium.defined(computeCommand.framebuffer) &&
      !Cesium.defined(computeCommand.outputTexture)
    ) {
      throw new Cesium.DeveloperError(
        "computeCommand.framebuffer or computeCommand.outputTexture is required.",
      );
    }

    const outputTexture = computeCommand.outputTexture ?? computeCommand.framebuffer._colorTextures[0];
    const width = outputTexture.width;
    const height = outputTexture.height;

    const context = this._context;
    const vertexArray = Cesium.defined(computeCommand.vertexArray)
      ? computeCommand.vertexArray
      : context.getViewportQuadVertexArray();
    const shaderProgram = Cesium.defined(computeCommand.shaderProgram)
      ? computeCommand.shaderProgram
      : createViewportQuadShader(context, computeCommand.fragmentShaderSource);
    const framebuffer = computeCommand.framebuffer ?? createFramebuffer(context, outputTexture);

    const renderState = createRenderState(width, height);
    const uniformMap = computeCommand.uniformMap;

    const clearCommand = clearCommandScratch;
    clearCommand.framebuffer = framebuffer;
    clearCommand.renderState = renderState;
    clearCommand.execute(context);

    const drawCommand = drawCommandScratch;
    drawCommand.vertexArray = vertexArray;
    drawCommand.renderState = renderState;
    drawCommand.shaderProgram = shaderProgram;
    drawCommand.uniformMap = uniformMap;
    drawCommand.framebuffer = framebuffer;
    drawCommand.execute(context);

    if (computeCommand.framebuffer) {
      //reset cache let _bind in
      if (outputTexture.depth > 1) {
        context._currentFramebuffer = undefined
      }
    } else {
      framebuffer.destroy();
    }

    if (!computeCommand.persists) {
      shaderProgram.destroy();
      if (Cesium.defined(computeCommand.vertexArray)) {
        vertexArray.destroy();
      }
    }

    if (Cesium.defined(computeCommand.postExecute)) {
      computeCommand.postExecute(outputTexture);
    }
  };
}

export default WeComputeEngine;
